/*
 * Log.c
 *      Author: dephille
 */

#include "Log.h"

#include <sys/stat.h>


/**
 * Sets the startTime for the given log by transforming the filename of the log into a UNIX timestamp.
 *
 * @param	log	The log.
 */
static void PC_Log_LoadStartTime(PC_Log* log) {
	const gchar*	slashPos;		// Clean-up: No
	const gchar*	dotPos;			// Clean-up: No
	gchar*			year;			// Clean-up: Yes
	gchar*			month;			// Clean-up: Yes
	gchar*			day;			// Clean-up: Yes
	gchar*			hour;			// Clean-up: Yes
	gchar*			minute;			// Clean-up: Yes
	gchar*			second;			// Clean-up: Yes
	gchar*			offsetHours;	// Clean-up: Yes
	gchar*			offsetMinutes;	// Clean-up: Yes

	// Check input:
	if(!log) {
		purple_debug_warning("PidginCote", "PC_Log_LoadStartTime: Missing 'log' parameter.\r\n");
		return;
	} else if (!log->fileName) {
		purple_debug_warning("PidginCote", "PC_Log_LoadStartTime: Missing 'log->fileName' parameter.\r\n");
		return;
	}

	// Check fileName:
	slashPos = g_strrstr(log->fileName, "/");
	dotPos = g_strrstr(log->fileName, ".");
	if (!slashPos || !dotPos) {
		purple_debug_error("PidginCote", "PC_Log_LoadStartTime: Filename without DOT or SLASH - %s\r\n", log->fileName);
		return;
	} else if ((slashPos + 26) != dotPos) {
		purple_debug_error("PidginCote", "PC_Log_LoadStartTime: Invalid fileName - %s\r\n", log->fileName);
		return;
	}
	slashPos++;

	// Detach parts:
	year = g_strndup(slashPos, 4);
	month = g_strndup(slashPos+5, 2);
	day = g_strndup(slashPos+8, 2);
	hour = g_strndup(slashPos+11, 2);
	minute = g_strndup(slashPos+13, 2);
	second = g_strndup(slashPos+15, 2);
	offsetHours = g_strndup(slashPos+18, 2);
	offsetMinutes = g_strndup(slashPos+20,2);

	// Build and assign time_t
	log->startTime = PC_Util_BuildTime(atoi(year), atoi(month), atoi(day), atoi(hour), atoi(minute), atoi(second));

	// Fix timezone offset:
	if ((slashPos + 17) == g_strrstr(slashPos, "-")) {
		log->startTime += ((3600 * atoi(offsetHours)) + (60 * atoi(offsetMinutes)));
	} else {
		log->startTime -= ((3600 * atoi(offsetHours)) + (60 * atoi(offsetMinutes)));
	}

	// Clean-up:
	g_free(year);
	g_free(month);
	g_free(day);
	g_free(hour);
	g_free(minute);
	g_free(second);
}



/**
 * Fetches the "Last Changed" UNIX timestamp of the given log from the filesystem.
 *
 * TODO: Check if a conversion to servertime is needed.
 *
 * @param	log	The log.
 */
static void PC_Log_LoadLastChange(PC_Log* log) {
	// Check input:
	if(!log) {
		purple_debug_warning("PidginCote", "PC_Log_FetchLastChange: Missing 'log' parameter.\r\n");
		return;
	}

	// Store:
	log->lastChange = PC_Util_GetMTime(log->fileName);
}



/**
 * Frees a log from memory.
 * 	This does not delete the log from the filesystem or the database.
 *
 * @param	log	The log.
 */
static void PC_Log_Free(PC_Log* log) {
	// Check input:
	if (!log) {
		return;
	}

	// Free fileName:
	if (log->fileName) {
		g_free(log->fileName);
		log->fileName = NULL;
	}

	// Free account:
	if (log->account) {
		g_free(log->account);
		log->account = NULL;
	}

	// Free protocol:
	if (log->protocol) {
		g_free(log->protocol);
		log->protocol = NULL;
	}

	// Free recipient:
	if (log->recipient) {
		g_free(log->recipient);
		log->recipient = NULL;
	}

	// Free object:
	g_free(log);
}



/**
 * Makes a new log object.
 *
 * @return The new Log.
 */
static PC_Log* PC_Log_New() {
	PC_Log*	newLog;	// Clean-up: No

	// Build object:
	newLog = g_new(PC_Log, 1);
	if (!newLog) {
		purple_debug_error("PidginCote", "PC_Log_New: Unable to allocate Log struct.\r\n");
		return NULL;
	}

	// Fill default values:
	newLog->protocol = NULL;
	newLog->account = NULL;
	newLog->recipient = NULL;
	newLog->startTime = 0;

	newLog->revision = 0;
	newLog->fileName = NULL;
	newLog->lastChange = 0;

	// Return:
	return newLog;
}



/**
 * Sets the revision number of a given log.
 *
 * @param	log			The log.
 * @param	revision	The new revision number for the log.
 */
static void PC_Log_SetRevision(PC_Log* log, unsigned int revision) {
	// Check input:
	if (!log) {
		purple_debug_warning("PidginCote", "PC_Log_SetRevision: Parameter 'log' not set.\r\n");
		return;
	}

	// Set:
	log->revision = revision;
}



/**
 * Writes a string to the given log.
 * 	If the log does not exist on the disk, it will be created.
 *
 * @param	log			The log.
 * @param	contents	The contents to write to the given log.
 */
static void PC_Log_Write(const PC_Log* log, const gchar* contents) {
	const gchar*	lastSlash;	// Clean-up: No
	gchar*			path;		// Clean-up: Yes

	// Check input:
	if (!log) {
		purple_debug_warning("PidginCote", "PC_Log_Write: Parameter 'log' not set.\r\n");
		return;
	} else if (!log->fileName) {
		purple_debug_warning("PidginCote", "PC_Log_Write: Parameter 'log->fileName' not set.\r\n");
		return;
	} else if (!contents) {
		purple_debug_warning("PidginCote", "PC_Log_Write: Parameter 'contents' not set.\r\n");
		return;
	}

	// Determine path:
	lastSlash = g_strrstr(log->fileName, "/");
	if (!lastSlash) {
		purple_debug_error("PidginCote", "PC_Log_Write: Unable to determine path: %s\r\n", log->fileName);
		return;
	}
	path = g_strndup(log->fileName, lastSlash - log->fileName);

	// Create path:
	if (purple_build_dir(path, 493) != 0) {
		purple_debug_warning("PidginCote", "PC_Log_Write: Unable to create path: %s\r\n", path);
	}

	// Clean-up:
	g_free(path);

	// Write:
	if(!g_file_set_contents(log->fileName, contents, -1, NULL)) {
		purple_debug_error("PidginCote", "PC_Log_Write: Unable to write log %s\r\n", log->fileName);
	}
}



/**
 * Reads the contents of a given log.
 *
 * @param	log	The log.
 * @return	The contents of the logfile or NULL if failed.
 */
static gchar* PC_Log_Read(const PC_Log* log) {
	gchar*	fileContents;	// Clean-up: No
	gsize	fileLength;		// Clean-up: No

	// Check input:
	if (!log) {
		purple_debug_warning("PidginCote", "PC_Log_Read: Parameter 'log' not set.\r\n");
		return NULL;
	} else if (!log->fileName) {
		purple_debug_warning("PidginCote", "PC_Log_Read: Parameter 'log->fileName' not set.\r\n");
		return NULL;
	}

	// Read:
	if (!g_file_get_contents(log->fileName, &fileContents, &fileLength, NULL)) {
		purple_debug_error("PidginCote", "PC_Log_Read: Unable to read log %s\r\n", log->fileName);
		return NULL;
	}

	// Return:
	return fileContents;
}



/**
 * Loads Log objects by inspecting a given folder.
 *
 * @param	path	The path containing the log files.
 * @param	protocol	The ProtocolID for the new log objects.
 * @param	account		The Account name for the new log objects.
 * @param	recipient	The recipient name for the new log objects.
 * @return	A GList with with the PC_Log objects contained in the given path or NULL if none were found.
 */
static GList* PC_Log_FromDir(const gchar* path, const gchar* protocol, const gchar* account, const gchar* recipient) {
	GDir*			logDir;			// Clean-up: Yes
	const gchar*	curName;		// Clean-up: No
	gchar*			curPath;		// Clean-up: Yes
	GList*			retList;		// Clean-up: No
	PC_Log*			curLog;			// Clean-up: No

	// Check input:
	if (!path) {
		purple_debug_warning("PidginCote", "PC_Log_FromDir: Missing 'path' parameter.\r\n");
		return NULL;
	}

	// Build the Log dir:
	logDir = g_dir_open(path, 0, NULL);
	if (!logDir) {
		purple_debug_info("PidginCote", "PC_Log_FromDir: Unable to open dir %s\r\n", path);
		return NULL;
	}

	// Traverse the dir:
	curName = g_dir_read_name(logDir);
	while (curName) {
		// Transform name into path:
		curPath = g_strdup_printf("%s/%s", path, curName);

		// Check if it is a directory:
		if (!g_file_test(curPath, G_FILE_TEST_IS_DIR)) {

			// Build PC_Log:
			curLog = PC_Log_New();
			if (!curLog) {

				// Failed:
				purple_debug_error("PidginCote", "PC_Log_FromDir: Unable to make PC_Log.\r\n");
			} else {

				// Fill in data:
				curLog->protocol = g_strdup(protocol);
				curLog->account = g_strdup(account);
				curLog->fileName = g_strdup(curPath);
				PC_Log_LoadLastChange(curLog);
				curLog->recipient = g_strdup(recipient);
				curLog->revision = 0;
				PC_Log_LoadStartTime(curLog);

				// Add to list:
				retList = g_list_prepend(retList, curLog);
			}
		}

		// Clean-up:
		g_free(curPath);

		// Next name:
		curName = g_dir_read_name(logDir);
	}


	// Clean-up:
	g_dir_close(logDir);

	// Return list:
	return retList;
}



/**
 * Makes a Log object from a given XML node (Should be a "<log>" element)
 *
 * @param	xmlLogNode	The XML node defining the log. (Usually a "<log>" element)
 * @param	A new Log object defined by the XML element or NULL if failed.
 */
static PC_Log* PC_Log_FromXML(xmlnode* xmlLogNode) {
	PC_Log*	retLog;	// Clean-up: No

	// Check input:
	if (!xmlLogNode) {
		purple_debug_warning("PidginCote", "PC_Log_FromXML: Missing 'xmlLogNode' parameter.\r\n");
		return NULL;
	}

	// Build log object:
	retLog = PC_Log_New();
	if (!retLog) {
		purple_debug_error("PidginCote", "PC_Log_FromXML: Unable to build PC_Log.\r\n");
		return NULL;
	}

	// Detach attributes:
	retLog->account = g_strdup(xmlnode_get_attrib(xmlLogNode, "account"));
	retLog->fileName = NULL;
	retLog->lastChange = 0;
	retLog->protocol = g_strdup(xmlnode_get_attrib(xmlLogNode, "protocol"));
	retLog->recipient = g_strdup(xmlnode_get_attrib(xmlLogNode, "recipient"));
	retLog->revision = atoi(xmlnode_get_attrib(xmlLogNode, "revision"));
	retLog->startTime = g_ascii_strtoull(xmlnode_get_attrib(xmlLogNode, "startDate"), NULL, 10);

	// Display some warnings:
	if (!retLog->account) {
		purple_debug_warning("PidginCote", "PC_Log_FromXML: Unable to fetch 'account' from XML node:\r\n%s\r\n", xmlnode_to_str(xmlLogNode, NULL));
	}
	if (!retLog->protocol) {
		purple_debug_warning("PidginCote", "PC_Log_FromXML: Unable to fetch 'protocol' from XML node:\r\n%s\r\n", xmlnode_to_str(xmlLogNode, NULL));
	}
	if (!retLog->recipient) {
		purple_debug_warning("PidginCote", "PC_Log_FromXML: Unable to fetch 'recipient' from XML node:\r\n%s\r\n", xmlnode_to_str(xmlLogNode, NULL));
	}
	if (!retLog->startTime) {
		purple_debug_warning("PidginCote", "PC_Log_FromXML: Unable to fetch 'startTime' from XML node:\r\n%s\r\n", xmlnode_to_str(xmlLogNode, NULL));
	}

	// Return Log:
	return retLog;
}



/**
 * Generates and sets a log's filename based upon its information.
 *
 * @param	log	The log.
 */
static void PC_Log_LoadFileName(PC_Log* log) {
	struct tm*		time;			// Clean-up: No
	gchar			buffer[128];	// Clean-up: No

	// Check input:
	if (!log) {
		purple_debug_warning("PidginCote", "PC_Log_LoadFileName: Missing 'log' parameter.\r\n");
		return;
	} else if(!log->protocol) {
		purple_debug_warning("PidginCote", "PC_Log_LoadFileName: Missing 'protocol' parameter.\r\n");
		return;
	} else if(!log->account) {
		purple_debug_warning("PidginCote", "PC_Log_LoadFileName: Missing 'account' parameter.\r\n");
		return;
	} else if(!log->recipient) {
		purple_debug_warning("PidginCote", "PC_Log_LoadFileName: Missing 'recipient' parameter.\r\n");
		return;
	} else if(!log->startTime) {
		purple_debug_warning("PidginCote", "PC_Log_LoadFileName: Missing 'startTime' parameter.\r\n");
		return;
	}

	// Destroy previous filename:
	if (log->fileName) {
		g_free(log->fileName);
	}

	// Build tm:
	time = gmtime(&(log->startTime));
	if (!time) {
		purple_debug_error("PidginCote", "PC_Log_LoadFileName: Unable to build TM.\r\n");
		return;
	}
	strftime(buffer, 128, "%Y-%m-%d.%H%M%S-0000GMT", time);

	// Assign filename:
	if (g_strstr_len(log->protocol, -1, "prpl-")) {
		log->fileName = g_strdup_printf("%s/logs/%s/%s/%s/%s.html", purple_user_dir(), (log->protocol) + 5, log->account, log->recipient, buffer);
	} else {
		log->fileName = g_strdup_printf("%s/logs/%s/%s/%s/%s.html", purple_user_dir(), log->protocol, log->account, log->recipient, buffer);
	}
}




/**
 * Makes a Log object from a mini-object.
 * 	Simply copies the MiniLog's attributes to the Log's attributes, does not auto-generate the other attributes.
 *
 * @param	miniLog	The miniLog to be converted to a PC_Log.
 * @returns	A new PC_Log or NULL if failed.
 */
static PC_Log*	PC_Log_FromMiniLog(const PC_MiniLog* miniLog) {
	PC_Log*	retLog;		// Clean-up: No

	// Check input:
	if (!miniLog) {
		purple_debug_warning("PidginCote", "PC_Log_FromMiniLog: Missing 'miniLog' parameter.\r\n");
		return NULL;
	}

	// Make new PC_Log
	retLog = PC_Log_New();
	if (!retLog) {
		purple_debug_error("PidginCote", "PC_Log_FromMiniLog: Unable to allocate new PC_Log.\r\n");
		return NULL;
	}

	// Copy attributes:
	retLog->lastChange = miniLog->lastChange;
	if (miniLog->fileName) {
		retLog->fileName = g_strdup(miniLog->fileName);
	}

	// Return log:
	return retLog;
}



/**
 * Fills the recipient attribute by looking at the fileName of the log.
 *
 * @param	log	The log to fetch the recipient for.
 */
static void PC_Log_LoadRecipient(PC_Log* log) {
	const gchar*	slash1;	// Clean-up: No
	const gchar*	slash2;	// Clean-up: No

	// Check input:
	if (!log) {
		purple_debug_warning("PidginCote", "PC_Log_LoadRecipient: Missing 'log' parameter.\r\n");
		return;
	} else if (!log->fileName) {
		purple_debug_warning("PidginCote", "PC_Log_LoadRecipient: Missing 'log->fileName' parameter.\r\n");
		return;
	}

	// Clean-up old recipient:
	if (log->recipient) {
		g_free(log->recipient);
		log->recipient = NULL;
	}

	// Find last slash:
	slash2 = g_strrstr(log->fileName, "/");
	if (!slash2) {
		purple_debug_error("PidginCote", "PC_Log_LoadRecipient: Unable to find last slash in fileName: %s\r\n", log->fileName);
		return;
	}

	// Find starting slash:
	slash1 = g_strrstr_len(log->fileName, slash2 - log->fileName - 1, "/");
	if (!slash1) {
		purple_debug_error("PidginCote", "PC_Log_LoadRecipient: Unable to find starting slash in fileName: %s\r\n", log->fileName);
		return;
	} else if (slash1 == slash2) {
		purple_debug_error("PidginCote", "PC_Log_LoadRecipient: Conflicting slash1 and slash2 in fileName: %s\r\n", log->fileName);
		return;
	}

	// Duplicate right portion of string:
	log->recipient = g_strndup(slash1 + 1, slash2 - slash1 - 1);
}




/**
 * Gets an SQL string which uniquely identifies the given log for use in a WHERE clause.
 * 	The primary key (recipient, startDate) is checked first and if not found, the filename is used.
 *
 * @param	log	The log.
 * @returns	An SQL string uniquely identifying the log or NULL if failed.
 */
static gchar* PC_Log_GetSQLID(const PC_Log* log) {
	char*	sqliteStr;	// Clean-up: Yes
	gchar*	retStr;		// Clean-up: No

	// Check input:
	if (!log) {
		purple_debug_warning("PidginCote", "PC_Log_GetSQLID: Missing 'log' argument.\r\n");
		return NULL;
	}

	// Can we use the primary key (recipient, startDate)?
	if (log->recipient) {
		sqliteStr = sqlite3_mprintf("recipient=%Q AND startDate=%i", log->recipient, log->startTime);
		retStr = g_strdup(sqliteStr);
		sqlite3_free(sqliteStr);
		sqliteStr = NULL;
		return retStr;
	}

	// Can we use the filename?
	if (log->fileName) {
		sqliteStr = sqlite3_mprintf("fileName=%Q", log->fileName);
		retStr = g_strdup(sqliteStr);
		sqlite3_free(sqliteStr);
		sqliteStr = NULL;
		return retStr;
	}

	// No unique identifier available:
	return NULL;
}
